package v1

import (
	"context"
	"fmt"
	"reflect"

	"github.com/go-logr/logr"
	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

// OwnDeployment
type OwnDeployment struct {
	Name string                `json:"name"`
	Type string                `json:"type"`
	Spec appsv1.DeploymentSpec `json:"spec"`
	//DiffSpec corev1.PodSpec        `json:"diffSpec"`
}

const (
	DefaultDeployment    string = "default"
	ControllerDeployment string = "controller"
)

func (o *OwnDeployment) MakeOwnResource(instance *IngressNginx, logger logr.Logger,
	scheme *runtime.Scheme) (interface{}, error) {

	// new a deployment object
	deployment := &appsv1.Deployment{
		// metadata field inherited from owner Unit
		//ObjectMeta: instance.ObjectMeta,
		ObjectMeta: metav1.ObjectMeta{
			Name:      o.Name,
			Namespace: instance.Namespace,
			Labels:    instance.Labels,
			Annotations: map[string]string{
				"99bill-webhook/validate": "false",
			},
		},
		Spec: o.Spec,
	}

	// add ControllerReference for deployment，the owner is Unit object
	if err := controllerutil.SetControllerReference(instance, deployment, scheme); err != nil {
		msg := fmt.Sprintf("set controllerReference for Deployment %s/%s failed", instance.Namespace, deployment.Name)
		logger.Error(err, msg)
		return nil, err
	}
	return deployment, nil
}

// Check if the Deployment already exists
func (o *OwnDeployment) OwnResourceExist(instance *IngressNginx, client client.Client,
	logger logr.Logger) (bool, interface{}, error) {

	found := &appsv1.Deployment{}
	err := client.Get(context.TODO(), types.NamespacedName{Name: o.Name, Namespace: instance.Namespace}, found)
	if err != nil {
		if errors.IsNotFound(err) {
			return false, nil, nil
		}
		msg := fmt.Sprintf("Deployment %s/%s found, but with error", instance.Namespace, o.Name)
		logger.Error(err, msg)
		return true, found, err
	}
	return true, found, nil
}

func (o *OwnDeployment) UpdateOwnResourceStatus(instance *IngressNginx, client client.Client,
	logger logr.Logger) (*IngressNginx, error) {

	// 获取deployment的状态
	found := &appsv1.Deployment{}
	err := client.Get(context.TODO(), types.NamespacedName{Name: o.Name, Namespace: instance.Namespace}, found)
	if err != nil {
		//msg := fmt.Sprintf("get Unit %s/%s own deployment status error", instance.Namespace, instance.Name)
		//logger.Error(err, msg)
		if errors.IsNotFound(err) {
			instance.Status.LastUpdateTime = metav1.Now()
			return instance, nil
		}
		return instance, err
	}

	// 将deployment的状态更新到Unit.status.deployment中
	if o.Type == ControllerDeployment {
		if !reflect.DeepEqual(instance.Status.ControllerDeployment, found.Status) {
			instance.Status.ControllerDeployment = found.Status
		}
	} else {
		if !reflect.DeepEqual(instance.Status.DefaultDeployment, found.Status) {
			instance.Status.DefaultDeployment = found.Status
		}
	}

	return instance, nil
}

// ApplyOwnResource is this own resource, create or update
func (o *OwnDeployment) ApplyOwnResource(instance *IngressNginx, client client.Client,
	logger logr.Logger, scheme *runtime.Scheme) error {
	// make deployment object
	deployment, err := o.MakeOwnResource(instance, logger, scheme)
	if err != nil {
		return err
	}
	newDeployment := deployment.(*appsv1.Deployment)
	//o.DiffSpec = newDeployment.Spec.Template.Spec

	// assert if deployment already exist
	exist, found, err := o.OwnResourceExist(instance, client, logger)
	if err != nil {
		return err
	}

	// apply the deployment object just make
	if !exist {
		// if deployment not exist，then create it
		msg := fmt.Sprintf("Deployment %s/%s not found, create it!", newDeployment.Namespace, newDeployment.Name)
		logger.Info(msg)
		if err := client.Create(context.TODO(), newDeployment); err != nil {
			msg := fmt.Sprintf("Create %s %s/%s error!", newDeployment.Kind, newDeployment.Namespace, newDeployment.Name)
			logger.Error(err, msg)
			return err
		}
		//exist, found, err = o.OwnResourceExist(instance, client, logger)
		// err != nil {
		//msg = fmt.Sprintf("First get %s deployment failed.", o.Type)
		//logger.Error(err, msg)
		//	return err
		//}

		//ie := found.(*appsv1.Deployment)
		//msg = fmt.Sprintf("First get controller deployment %s", tmpDeploy)
		//logger.Info(msg)
		//if o.Type == ControllerDeployment {
		//	instance.Status.ControllerDeployment = ie.Status
		//} else {
		//	instance.Status.DefaultDeployment = ie.Status
		//}

		return nil

	}
	foundDeployment := found.(*appsv1.Deployment)
	if !reflect.DeepEqual(newDeployment.Spec.Template.Spec.Containers[0].Image, foundDeployment.Spec.Template.Spec.Containers[0].Image) {
		msg := fmt.Sprintf("Updating Deployment %s/%s", newDeployment.Namespace, newDeployment.Name)
		logger.Info(msg)
		return client.Update(context.TODO(), newDeployment)
	}
	return nil

}
